#ifndef _SHADER_H_
#define _SHADER_H_


#include <SERender/Util/Util.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <variant>
#include <string>
#include <unordered_map>
#include <SERender/Resourses/Texture.h>
#include <memory>
#include <SECore/Util.h>
#include <array>
#include <SECore/ResourcePayload.h>
#include <optional>
#include <map>
#include <SECore/OrderedMap.h>

namespace SEngine
{
    enum class TextureSlot {
        TEXTURE0 = 0,
        TEXTURE1,
        TEXTURE2,
        TEXTURE3,
        TEXTURE4,
        TEXTURE5,
        TEXTURE6,
        TEXTURE7,
        TEXTURE8,
        TEXTURE9,
        TEXTURE10,
        TEXTURE11,
        TEXTURE12,
        TEXTURE13,
        TEXTURE14,
        TEXTURE15,
        TEXTURE16,
        TEXTURE17,
        TEXTURE18,
        TEXTURE19,
        TEXTURE20,
        TEXTURE21,
        TEXTURE22,
        TEXTURE23,
        TEXTURE24,
        TEXTURE25,
        TEXTURE26,
        TEXTURE27,
        TEXTURE28,
        TEXTURE29,
        TEXTURE30,
        TEXTURE31,
    };

    class ShaderLoader;

    class Shader
    {
    public:
        struct Sampler { 
            std::optional<std::reference_wrapper<Texture>> texture;
            TextureSlot id = TextureSlot::TEXTURE31; 
            std::string path;
        };
        friend class ShaderLoader;
        using UniformLoader = TypesLoader<int, float, glm::vec2, glm::vec3, glm::vec4, glm::mat3, glm::mat4, Sampler>;
        using UniformType = UniformLoader::Variant;
    public:
        Shader();
        virtual ~Shader();
        void Use();

        uint32_t GetId() { return m_id; }

        /**
         * F = std::function<void(const std::string &, auto &)>;
         */
        template<typename F>
        void ForeachUniforms(F && fn) {
            m_uniformInfos.Foreach([fn](auto & name, auto & type) {
                VariantGet(type, [name, fn](auto & t) { fn(name, t); });
            });
        }

        template<typename T>
        void SetUniform(const std::string & name, T t) {
            int idx = FindLocationIndex(name);
            if (idx != -1) {
                SetUniform_(idx, t);
            }
        }

        template<>
        void SetUniform(const std::string & name, Sampler t) {
            int idx = FindLocationIndex(name);
            if (idx != -1) {
                t.id = std::get<Sampler>(m_uniformInfos.At(name)).id;
                SetUniform_(idx, t);
            }
        }

        bool HasUniform(const std::string & name) {
            return m_uniformInfos.Has(name);
        }
    private:
        int FindLocationIndex(const std::string & name);
        void SetUniform_(int idx, int t);
        void SetUniform_(int idx, float t);
        void SetUniform_(int idx, glm::vec2 t);
        void SetUniform_(int idx, glm::vec3 t);
        void SetUniform_(int idx, glm::vec4 t);
        void SetUniform_(int idx, glm::mat3 t);
        void SetUniform_(int idx, glm::mat4 t);
        void SetUniform_(int idx, Sampler sampler2D);
        
    private:
        uint32_t m_id;
        OrderedMap<std::string, UniformType> m_uniformInfos;
    };
} // namespace SEngine



#endif
